/**
 * OSAmI: Open Source Ambient Intelligence
 * A simple discovery protocol (based on multicast communications) for constrained devices.
 * Copyright (C) Lionel Debroux 2006 (as a TER/Magistere M1 student in the LSR-IMAG-ADELE team, for a demo in the H-Omega project)
 * Copyright (C) Lionel Debroux 2010 (as an engineer in the LCIS-INPG-CTSYS team, for a pre-demo in the FR part of the OSAMI project)
 * Contact: osami-general@osami-commons.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation;
 * version 2.1 of the License.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Suite 500 Boston, MA 02110-1335 USA
 */

package fr.imag.adele.homega.impl.unidiscovery;

import java.io.IOException;
import java.util.Dictionary;
import java.util.Hashtable;
import java.util.logging.Logger;

import fr.imag.adele.homega.impl.unicast.UnicastHandler;
import fr.imag.adele.homega.impl.unicast.UnicastHandlerImpl;
import fr.imag.adele.homega.impl.unicast.UnicastSender;
import fr.imag.adele.homega.impl.unicast.UnicastSenderImpl;
import fr.imag.adele.homega.impl.unicast.UnicastReceiver;
import fr.imag.adele.homega.impl.unicast.UnicastReceiverImpl;
import fr.imag.adele.homega.impl.unidiscovery.Device;
import fr.imag.adele.homega.impl.unidiscovery.DeviceEvent;
import fr.imag.adele.homega.impl.unidiscovery.DeviceMetadata;

/**
 * This class implements the discovery bridge side of a simple protocol inspired by WS-Discovery (but MUCH easier to implement and EXTREMELY more lightweight !).
 */
public class DiscoveryBridgeImpl
	extends UnicastReceiverImpl
	implements DiscoveryBridge, Comparable {

	private UnicastSender sender = null;
	private UnicastHandler handler = null;
	private String topic = null;
	private int port = 0;
	private UnidiscoveryBridgeMetadata metadata = null;
	private BundleDeployer deployer;
	private EventAdmin ea;
	private boolean hasQuit = true;

	/**
	 * Constructor
	 * @param topic 
	 * @param port
	 * @param deployer object whose class implements an OSGi bundle deployer
	 *        (deployBundle + start). NULL if not in an OSGi environment.
	 * @param deployer object whose class implements an OSGi event admin
	 *        (postEvent). NULL if not in an OSGi environment.
	 * @throws Exception
	 */
	public DiscoveryBridgeImpl(String topic, int port, BundleDeployer deployer, EventAdmin ea) throws Exception {
		sender = new UnicastSenderImpl("DiscoveryBridge");
		handler = new UnicastHandlerImpl();
		handler.register(topic, port, this);
		this.topic = topic;
		this.port = port;

		metadata = new UnidiscoveryBridgeMetadata();

		// Don't forget to bind to given address.
		sender.bind(topic, 12345);

		this.deployer = deployer;
		this.ea = ea;
		hasQuit = false;
	}

	public synchronized void getMetadata(String ipAddr, int _port) throws IOException {
		sender.send(ipAddr, _port, Device.DEVICE_GETMETADATA_PREFIX + this.topic + Device.DEVICE_FIELD_SEPARATOR + this.port + Device.DEVICE_FIELD_SEPARATOR);
		Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).info("Sent \u001B[1;31mGETMETADATA\u001B[m message !");
	}

	public synchronized void probe(String type) throws IOException {
		sender.send(this.topic, this.port, Device.DEVICE_PROBE_PREFIX + type + Device.DEVICE_FIELD_SEPARATOR);
		Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).info("Sent \u001B[1;31mPROBE\u001B[m message !");
	}

	public synchronized void shutdown() {
		if (handler != null) {
			handler.shutdown();
		}
		if (sender != null) {
			sender.shutdown();
		}
		hasQuit = true;
	}

	public UnicastHandler getHandler() {
		return handler;
	}

	public synchronized void receive(String _topic, String message) {
		String [] strings;
		String message2;
		String id;
		Dictionary<String,String> dict;

		//Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).info("Bridge received \"" + message + "\"");

		if (message != null) {

			// HELLO
			if (message.startsWith(Device.DEVICE_HELLO_PREFIX)) {
				message2 = message.substring(Device.DEVICE_HELLO_PREFIX.length());
				strings = message2.split(Device.DEVICE_FIELD_SEPARATOR);

				if (strings.length >= 3) {

					id = strings[0];
					/*try {
						id = UUID.fromString(strings[0]);
					}
					catch (IllegalArgumentException e) {
						Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).warning("Malformed HELLO message, incorrect id \"" + strings[0] + "\"");
						return;
					}*/

					Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).info("Received \u001B[1;31mHELLO\u001B[m message from device with id \"" + id + "\" !");

					try {
						// IP, port.
						metadata.addDevice(id, strings[1], Integer.parseInt(strings[2]));

						this.getMetadata(strings[1], Integer.parseInt(strings[2]));
					} catch (NumberFormatException e) {
						Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).warning("Malformed port in HELLO message !\n");
						e.printStackTrace();
					} catch (IOException e) {
						e.printStackTrace();
					}
				}
				else {
					Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).warning("Malformed HELLO message \"" + message + "\"");
				}
			}


			// METADATA
			else if (message.startsWith(Device.DEVICE_METADATA_PREFIX)) {
				message2 = message.substring(Device.DEVICE_METADATA_PREFIX.length());
				strings = message2.split(Device.DEVICE_FIELD_SEPARATOR);

				if (strings.length >= 2) {
					id = strings[0];
					/*try {
						id = UUID.fromString(strings[0]);
					}
					catch (IllegalArgumentException e) {
						Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).warning("Malformed METADATA message, incorrect id \"" + strings[0] + "\"");
						return;
					}*/

					Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).info("Received \u001B[1;31mMETADATA\u001B[m message from device with id \"" + id + "\" !");

					message2 = strings[1];

					metadata.parseAddMetadata(id, message2);

					// Deploy appropriate bridge automatically.
					if (this.deployer != null) {
						String location = metadata.getMetadata(id,"BridgeURL");

						if ((location != null) && (!(location.equals("")))) {
							try {
								this.deployer.deployBundle(location);
							} catch (Exception e) {
								Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).warning("Couldn't deploy bundle for device with id \"" + id + "\" !\n" +
										"Location was: \"" + location + "\".");
								e.printStackTrace();
								return;
							}

							Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).info("Bundle was deployed ");
						}
						else {
							Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).warning("Couldn't deploy bundle for device with id \"" + id + "\": no \"BridgeURL\" property !");
						}
					}
				}
				else {
					Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).warning("Malformed METADATA message \"" + message + "\"");
				}
			}


			// BYE
			else if (message.startsWith(Device.DEVICE_BYE_PREFIX)) {
				message2 = message.substring(Device.DEVICE_BYE_PREFIX.length());
				strings = message2.split(Device.DEVICE_FIELD_SEPARATOR);

				if (strings.length >= 3) {

					id = strings[0];
					/*try {
						id = UUID.fromString(strings[0]);
					}
					catch (IllegalArgumentException e) {
						Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).warning("Malformed BYE message, incorrect id \"" + strings[0] + "\"");
						return;
					}*/

					Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).info("Received \u001B[1;31mBYE\u001B[m message from device with id \"" + id + "\" !");

					try {
						// IP, port.
						metadata.removeDevice(id, strings[1], Integer.parseInt(strings[2]));
					} catch (NumberFormatException e) {
						Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).warning("Malformed port in BYE message !\n");
						e.printStackTrace();
					}
				}
				else {
					Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).warning("Malformed BYE message \"" + message + "\"");
				}
			}


			// PROBE
			else if (message.startsWith(Device.DEVICE_PROBE_PREFIX)) {
				// Discard probe messages.
			}


			// EVENT
			else if (message.startsWith(Device.DEVICE_EVENT_PREFIX)) {
				message2 = message.substring(Device.DEVICE_EVENT_PREFIX.length());
				strings = message2.split(Device.DEVICE_FIELD_SEPARATOR);

				if (strings.length >= 2) {
					id = strings[0];
					/*try {
						id = UUID.fromString(strings[0]);
					}
					catch (IllegalArgumentException e) {
						Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).warning("Malformed EVENT message, incorrect id \"" + strings[0] + "\"");
						return;
					}*/

					Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).info("Received \u001B[1;31mEVENT\u001B[m message from device with id \"" + id + "\" !");


					message2 = strings[1];

					dict = DeviceEvent.parseEvent(message2);

					// Send event to EA.
					if (this.ea != null) {
						String location = dict.get((Object)"Topic");

						if ((location != null) && (!(location.equals("")))) {
							try {
								this.ea.postEvent(location,dict);
							} catch (Exception e) {
								Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).warning("Couldn't post event for device with id \"" + id + "\" !\n" +
										"Location was: \"" + location + "\".");
								e.printStackTrace();
								return;
							}

							Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).info("Event was posted ");
						}
						else {
							Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).warning("Couldn't post event for device with id \"" + id + "\": no \"Topic\" property !");
						}
					}
				}
				else {
					Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).warning("Malformed EVENT message \"" + message + "\"");
				}
			}

			else {
				// Not a special message.
				System.out.println("In topic \"" + _topic +
						"\", bridge received message:\n\"" + message +"\"");
			}

		}
	}

	// Debug method: print metadata.
	public synchronized String getAllMetadata() {
		return metadata.toString();
	}

	// XXX is that correct ?
	public int compareTo(Object o) {
		return 1;
	}

	/**
	 * Bug catcher...
	 */
	@Override
	public void finalize() {
		if (!hasQuit) {
			System.out.println("\u001B[1;5;31mForgot to terminate the discovery bridge for " + topic + " port " + port + " !\u001B[m");
			shutdown();
		}
	}
}
